package com.zhj.helloframework.aop;

import com.zhj.helloframework.aop.aspect.AspectInfo;
import com.zhj.helloframework.util.ValidationUtil;
import lombok.Getter;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * @Author: zhj
 * @Description: 实现增强逻辑的类，这个类的增强功能的实现利用的是cglib
 * @Date: 2021/10/8 16:55
 * @Version: 1.0
 */
public class AspectListExecutor implements MethodInterceptor {

    // 存储要增强的类所对应的类对象，作用是传递给我们自己定义的切面类中的方法，交由自定义的方法去操作
    private Class<?> targetClass;

    // 存储targetClass类进行增强的切面类的列表，该列表中的数据应该是按照order从小到大排序
    @Getter
    private List<AspectInfo> sortedAspectInfoList;

    /**
     * 进行赋值操作
     * @param targetClass 要增强的类所对应的类对象
     * @param sortedAspectInfoList 该数据是未经过筛选的
     */
    public AspectListExecutor(Class<?> targetClass,List<AspectInfo> sortedAspectInfoList) {
        this.targetClass = targetClass;
        // 调用排序方法，ps：这里可以直接先排序然后赋值，此处另外添加一个方法使为了美观
        this.sortedAspectInfoList = sortAspectInfoList(sortedAspectInfoList);
    }

    /**
     * 按照order的值进行升序排序，确保Order值小的aspect先被织入
     * @param aspectInfoList
     * @return
     */
    private List<AspectInfo> sortAspectInfoList(List<AspectInfo> aspectInfoList) {
        // 升序排列
        Collections.sort(aspectInfoList, new Comparator<AspectInfo>() {
            @Override
            public int compare(AspectInfo o1, AspectInfo o2) {
                return o1.getOrderIndex() - o2.getOrderIndex();
            }
        });
        return aspectInfoList;
    }

    /**
     *
     * @param obj 被代理的对象
     * @param method 被拦截的方法
     * @param objects 被拦截的方法的参数
     * @param methodProxy MethodProxy类型的被拦截的方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 存储被代理的方法的返回值
        Object returnValue = null;

        // 筛选出能够对该方法进行增强的切面类
        collectAccurateMatchedAspectList(method);

        // 如果没有能够对该方法进行增强的切面类，就直接执行该方法并返回
        if (ValidationUtil.isEmpty(sortedAspectInfoList)) {
            return methodProxy.invokeSuper(obj,objects);
        }
        // 1. 按照order的顺序升序执行完所有Aspect的before方法
        invokeBeforeAdvices(method,objects);
        try {
            // 2. 执行被代理类的方法
            returnValue = methodProxy.invokeSuper(obj,objects);
            // 3. 如果被代理方法正常返回，则按照order的顺序降序执行完所有Aspect的afterReturning方法
            invokeAfterReturningAdvices(method,objects,returnValue);
        } catch (Exception e) {
            // 4. 如果被代理方法抛出异常，则按照order的顺序降序执行完所有Aspect的afterThrowing方法
            invokeAfterThrowingAdvices(method,objects,e);
        }
        return returnValue;
    }

    /**
     * 进行精确筛选
     * @param method 被拦截的方法
     */
    private void collectAccurateMatchedAspectList(Method method) {
        if (ValidationUtil.isEmpty(sortedAspectInfoList)) {
            return;
        }
        Iterator<AspectInfo> iterator = sortedAspectInfoList.iterator();
        while (iterator.hasNext()) {
            AspectInfo aspectInfo = iterator.next();
            if (!aspectInfo.getPointcutLocator().accurateMatches(method)) {
                // 如果该切面类不能对该方法进行增强，那么从列表中移除该切面类
                iterator.remove();// 需要用Iterator自带的remove，不能用sortedAspectInfoList的remove，否则就会出现并发修改异常
            }
        }
    }

    private void invokeBeforeAdvices(Method method,Object[] args) throws Throwable {
        for (AspectInfo aspectInfo : sortedAspectInfoList) {
            aspectInfo.getAspectObject().before(targetClass,method,args);
        }
    }

    private void invokeAfterReturningAdvices(Method method,Object[] args,Object returnValue) throws Throwable {
        for (int i = sortedAspectInfoList.size() - 1; i >= 0; i --) {
            sortedAspectInfoList.get(i).getAspectObject().afterReturning(targetClass,method,args,returnValue);
        }
    }

    private void invokeAfterThrowingAdvices(Method method,Object[] args,Exception e) throws Throwable {
        for (int i = sortedAspectInfoList.size() - 1; i >= 0; i --) {
            sortedAspectInfoList.get(i).getAspectObject().afterThrowing(targetClass,method,args,e);
        }
    }
}
